#include "StdAfx.h"
#include "ConsoleHotUpdate.h"
#include "ConsoleConnection.h"
#include "ConsoleSync.h"
#include "GameExporter.h"
#include "ResourceCompilerHelper.h"
#include "GameEngine.h"

std::map<CString,CConsoleHotUpdate::SHotSyncFileType>	CConsoleHotUpdate::s_fileTypes;
ULONGLONG																							CConsoleHotUpdate::s_tempFileUidCounter = 0;

size_t CConsoleHotUpdate::SHotUpdateJob::ComputeFileCopyCmdCount()
{
	size_t count = 0;

	for( size_t i = 0, iCount = m_commands.size(); i < iCount; ++i )
	{
		if( m_commands[i]->m_type == SHotUpdateCommand::eType_FileCopy )
		{
			++count;
		}
	}

	return count;
}

//---

CConsoleHotUpdate::CConsoleHotUpdate()
{
	// file types map of: ( fileExtension ) -> ( compileOptions, Ignorable, compileStages, outExtensions )
	s_fileTypes["dds"] = SHotSyncFileType( NULL, false, 1, "dds" );
	s_fileTypes["tif"] = SHotSyncFileType( NULL, false, 2, "dds" );
	s_fileTypes["mtl"] = SHotSyncFileType( "/overwriteextension=xml", false, 0, "mtl,binxml,binmtl" );
	s_fileTypes["xml"] = SHotSyncFileType( NULL, false, 1, "xml,binxml" );
	s_fileTypes["ent"] = SHotSyncFileType( "/overwriteextension=xml", false, 1, "ent" );
	s_fileTypes["dlg"] = SHotSyncFileType( "/overwriteextension=xml", false, 1, "dlg" );
	s_fileTypes["cgf"] = SHotSyncFileType( NULL, false, 1, "cgf" );
	s_fileTypes["cga"] = SHotSyncFileType( NULL, false, 1, "cga" );
	s_fileTypes["chr"] = SHotSyncFileType( NULL, false, 1, "chr" );
	s_fileTypes["cba"] = SHotSyncFileType( "/cleanupfast=1", false, 1, "cba" );
	s_fileTypes["cdf"] = SHotSyncFileType( NULL, false, 0, "cdf" );
	s_fileTypes["animevents"] = SHotSyncFileType(  "/overwriteextension=xml", false, 1, "xml,binxml" );
	s_fileTypes["pak"] = SHotSyncFileType( NULL, false, 0, NULL );
	s_fileTypes["max"] = SHotSyncFileType( NULL, true, 0, NULL );
	s_fileTypes["psd"] = SHotSyncFileType( NULL, true, 0, "tif,dds" );
	s_fileTypes["bak"] = SHotSyncFileType( NULL, true, 0, NULL );
	s_fileTypes["lyr"] = SHotSyncFileType( NULL, true, 0, NULL );
	s_fileTypes["cry"] = SHotSyncFileType( NULL, true, 0, NULL );
	s_fileTypes["cry$"] = SHotSyncFileType( NULL, true, 0, NULL );
	s_fileTypes["tmp"] = SHotSyncFileType( NULL, true, 0, NULL );
	s_fileTypes["tmp$"] = SHotSyncFileType( NULL, true, 0, NULL );
	s_fileTypes["pak$"] = SHotSyncFileType( NULL, true, 0, NULL );
	s_fileTypes["bai"] = SHotSyncFileType( NULL, true, 0, NULL );
	m_bRun = false;
	m_bPaused = false;
	m_pCurrentJob = NULL;
	m_strConsoleHotUpdateTempFolder = "Temp\\ConsoleHotUpdate\\";
	m_hExportAndLaunchJob = 0;

	// delete any temp files left
	CFileUtil::Deltree( m_strConsoleHotUpdateTempFolder, true );
	// create the console hot update temp folder
	CFileUtil::CreatePath( m_strConsoleHotUpdateTempFolder );
	// start job commands process thread
	Begin();
}

CConsoleHotUpdate::~CConsoleHotUpdate()
{
	// end job commands process thread
	End();

	//Wait for thread to finish before destroying
	WaitForThread();

	// delete remaining jobs
	std::vector<SHotUpdateJob*>::iterator iter = m_jobs.begin();

	while( iter != m_jobs.end() )
	{
		delete *iter;
		++iter;
	}
}

void CConsoleHotUpdate::Begin()
{
	m_bPaused = false;

	if( m_bRun )
	{
		return;
	}

	Start( -1, "ConsoleHotUpdate" );
}

void CConsoleHotUpdate::End()
{
	m_bRun = false;
	m_bPaused = false;
	Stop();
}

void CConsoleHotUpdate::Resume()
{
	m_syncMutex.Unlock();
	m_bPaused = false;
	Begin();
}

void CConsoleHotUpdate::AbortJob( TJobHandle hJob )
{
	m_syncMutex.Lock();
	m_jobsToAbort.insert( hJob );
	m_syncMutex.Unlock();
}

bool CConsoleHotUpdate::DeleteJob( TJobHandle hJob )
{
	if( !hJob )
	{
		return true;
	}

	m_syncMutex.Lock();
	m_jobsToDelete.insert( hJob );
	m_syncMutex.Unlock();

	return true;
}

bool CConsoleHotUpdate::IsJobDone( TJobHandle hJob )
{
	bool bDone = true;

	if( !hJob )
	{
		return true;
	}
	
	m_syncMutex.Lock();
	std::map<TJobHandle,bool>::iterator iter = m_jobsDone.find( hJob );

	if( iter != m_jobsDone.end() )
	{
		bDone = iter->second;
	}
	
	m_syncMutex.Unlock();

	return bDone;
}

void CConsoleHotUpdate::CommandProcessingPause()
{
	m_bPaused = true;
}

void CConsoleHotUpdate::CommandProcessingResume()
{
	m_bPaused = false;
}

CConsoleHotUpdate::TJobHandle CConsoleHotUpdate::AddFile( const char* pFilename, TJobHandle hJob, SHotUpdateJobObserver* pObserver )
{
	CString strFileExt = Path::GetExt( pFilename );

	if( IgnoreFileExtension( strFileExt ) )
	{
		Log( "ConsoleHotUpdate: Ignoring file type '%s'", pFilename );
		return 0;
	}

	CString newFilename;

	// generate new unique number! 64bit should be unique enough per editor session
	++s_tempFileUidCounter;

	CryAutoLock<CryMutex> autoLockSync( m_syncMutex );

	// deal with jobless and observerless command, add it to main job
	if( !hJob && !pObserver )
	{
		if( IsFileAlreadyInQueue( &m_mainJobInfo, pFilename ) )
		{
			return 0;
		}

		m_mainJobInfo.m_commands.push_back( new SHotUpdateCommandFileCopy( s_tempFileUidCounter, pFilename, GetTickCount() ) );
		m_mainJobInfo.m_pObserver = pObserver;
	}
	else
	{
		// create new job if none
		SHotUpdateJob* pJob = NULL;

		// if no job specified, create new one
		if( !hJob )
		{
			pJob = new SHotUpdateJob();
			m_newJobs.push_back( pJob );
		}
		else
		{
			// otherwise use the the existing given job
			pJob = (SHotUpdateJob*)hJob;
		}

		assert( pJob );

		if( pJob )
		{
			// if its already in the job's queue, skip it
			if( IsFileAlreadyInQueue( pJob, pFilename ) )
			{
				return (TJobHandle)pJob;
			}

			SHotUpdateCommand* pCmd = new SHotUpdateCommandFileCopy( s_tempFileUidCounter, pFilename, GetTickCount() );

			if( !hJob )
			{
				pJob->m_commands.push_back( pCmd );
				pJob->m_pObserver = pObserver;
				hJob = (TJobHandle)pJob;
			}
			else
			{
				m_newJobCommands[pJob].push_back( pCmd );
			}
		}
	}

	autoLockSync.~CryAutoLock();
	Resume();

	return hJob;
}

CConsoleHotUpdate::TJobHandle CConsoleHotUpdate::AddFiles( const std::set<CString>& rFilenames, TJobHandle hJob, SHotUpdateJobObserver* pObserver )
{
	CString newFilename;
	SHotUpdateJob* pJob = NULL;
	bool bExistingJob = false;

	if (!gSettings.oHotUpdateSystemSettings.IsAnyPlatformEnabled())
		return NULL;

	CryAutoLock<CryMutex> autoLockSync( m_syncMutex );

	if( !hJob && !pObserver )
	{
		pJob = &m_mainJobInfo;
	}
	else
	{
		if( !hJob )
		{
			pJob = new SHotUpdateJob();
			m_newJobs.push_back( pJob );
		}
		else
		{
			bExistingJob = true;
			pJob = (SHotUpdateJob*)hJob;
		}

		assert( pJob );

		if( !pJob )
		{
			return 0;
		}

		pJob->m_pObserver = pObserver;
		hJob = (TJobHandle)pJob;
	}

	for( std::set<CString>::const_iterator iter = rFilenames.begin(), iterEnd = rFilenames.end(); iter != iterEnd; ++iter )
	{
		CString strFileExt = Path::GetExt( (*iter) );

		if( IgnoreFileExtension( strFileExt ) )
		{
			Log( "ConsoleHotUpdate: Ignoring file type '%s'", (const char*)(*iter) );
			continue;
		}

		if( IsFileAlreadyInQueue( pJob, (*iter) ) )
		{
			continue;
		}

		Log( "ConsoleHotUpdate: Adding '%s'", (const char*)(*iter) );

		// generate new unique number! 64bit should be unique enough per editor usage session
		SHotUpdateCommand* pCmd = new SHotUpdateCommandFileCopy( ++s_tempFileUidCounter, *iter, GetTickCount() );

		if( !bExistingJob )
		{
			pJob->m_commands.push_back( pCmd );
		}
		else
		{
			m_newJobCommands[pJob].push_back( pCmd );
		}
	}

	autoLockSync.~CryAutoLock();
	Resume();

	return hJob;
}

CConsoleHotUpdate::TJobHandle CConsoleHotUpdate::AddResetCommand( bool bHardReset, TJobHandle hJob /* = 0 */, SHotUpdateJobObserver* pObserver /* = NULL */ )
{
	CryAutoLock<CryMutex> autoLockSync( m_syncMutex );

	SHotUpdateCommand::EType aCmdType = bHardReset ? SHotUpdateCommand::eType_ResetHard : SHotUpdateCommand::eType_ResetSoft;

	if( !hJob && !pObserver )
	{
		m_mainJobInfo.m_commands.push_back( new SHotUpdateCommand( aCmdType, GetTickCount() ) );
		m_mainJobInfo.m_pObserver = pObserver;
	}
	else
	{
		SHotUpdateJob* pJob = NULL;

		if( !hJob )
		{
			pJob = new SHotUpdateJob();
			m_newJobs.push_back( pJob );
		}
		else
		{
			pJob = (SHotUpdateJob*)hJob;
		}

		assert( pJob );

		if( pJob )
		{
			SHotUpdateCommand* pCmd = new SHotUpdateCommand( aCmdType, GetTickCount() );

			if( !hJob )
			{
				pJob->m_commands.push_back( pCmd );
				pJob->m_pObserver = pObserver;
			}
			else
			{
				m_newJobCommands[pJob].push_back( pCmd );
			}

			hJob = (TJobHandle)pJob;
		}
	}

	autoLockSync.~CryAutoLock();
	Resume();

	return hJob;
}

CConsoleHotUpdate::TJobHandle CConsoleHotUpdate::AddLaunchCommand( TJobHandle hJob /* = 0 */, SHotUpdateJobObserver* pObserver /* = NULL */ )
{
	CryAutoLock<CryMutex> autoLockSync( m_syncMutex );

	if( !hJob && !pObserver )
	{
		m_mainJobInfo.m_commands.push_back( new SHotUpdateCommand( SHotUpdateCommand::eType_LaunchGame, GetTickCount() ) );
		m_mainJobInfo.m_pObserver = pObserver;
	}
	else
	{
		SHotUpdateJob* pJob = NULL;

		if( !hJob )
		{
			pJob = new SHotUpdateJob();
			m_newJobs.push_back( pJob );
		}
		else
		{
			pJob = (SHotUpdateJob*)hJob;
		}

		assert( pJob );

		if( pJob )
		{
			SHotUpdateCommand* pCmd = new SHotUpdateCommand( SHotUpdateCommand::eType_LaunchGame, GetTickCount() );

			if( !hJob )
			{
				pJob->m_commands.push_back( pCmd );
				pJob->m_pObserver = pObserver;
			}
			else
			{
				m_newJobCommands[pJob].push_back( pCmd );
			}

			hJob = (TJobHandle)pJob;
		}
	}

	autoLockSync.~CryAutoLock();
	Resume();

	return hJob;
}

bool CConsoleHotUpdate::ResetConsoles( bool bHard )
{
	for( size_t i = CHotUpdateSystem::eFirstPlatform;	i < CHotUpdateSystem::eNumberOfPlatforms; ++i )
	{
		CHotUpdateSystem::EConsolePlatform aPlatform =	(CHotUpdateSystem::EConsolePlatform)i;

		if( !gSettings.oHotUpdateSystemSettings.boPlatformEnabled[i] )
		{
			continue;
		}

		//TODO: add hard/soft reset
		if( !GetIEditor()->GetConsoleSync()->GetConsoleConnection()->Reset( aPlatform ) )
		{
			return false;
		}
	}

	return true;
}

bool CConsoleHotUpdate::PrepareFileForTransfer( ULONGLONG uid, const char* pFilename, CString& rOutNewFilename, bool& bExistsInPak /*[out]*/, CString& rAdjustedOriginalFilename )
{
	CFileUtil::FileDesc desc;
	CCryFile cryfile;
	CString strPakPath;

	bExistsInPak = false;

	// try to open the file to see if its in a PAK
	if( cryfile.Open( pFilename, "rb" ) )
	{
		bExistsInPak = cryfile.IsInPak();
		
		if( bExistsInPak )
		{
			strPakPath = Path::GetPath( cryfile.GetPakPath() );
		}

		cryfile.Close();
	}

	rOutNewFilename = pFilename;

	if( !bExistsInPak )
	{
		CString ext = Path::GetExt( rOutNewFilename ), extLower;

		extLower = ext;
		extLower.MakeLower();

		// check for tif extension, and if found, try to find dds files
		if( ext == "tif" || ext == "tiff" )
		{
			rOutNewFilename.Replace( ext, "dds" );

			// try to open the file again, to see if its in a PAK
			if( cryfile.Open( rOutNewFilename, "rb" ) )
			{
				bExistsInPak = cryfile.IsInPak();

				if( bExistsInPak )
				{
					strPakPath = Path::GetPath( cryfile.GetPakPath() );
				}

				cryfile.Close();
			}
		}
	}

	CString strTempFile, strRoot, strExt;

	// extract to temp folder
	Log( "ConsoleHotUpdate: Extracting file: '%s'", rOutNewFilename.GetBuffer() );

	// if its in a PAK, deal properly with the path
	if( bExistsInPak )
	{
		// make the file path to copy to on consoles
		strTempFile = Path::AddBackslash( strPakPath );
		strTempFile += rOutNewFilename;
		rAdjustedOriginalFilename = strTempFile;

		// make the file path to extract to on PC temp folder
		strTempFile = "Temp/";
		strTempFile += rAdjustedOriginalFilename;
	}
	else
	{
		// otherwise if not in PAK, its the same as original filename
		strTempFile = rOutNewFilename;
	}

	CString originalFile = rOutNewFilename;

	if( !CFileUtil::ExtractFile( rOutNewFilename, false, strTempFile.GetLength() ? strTempFile : NULL ) )
	{
		Log( "ConsoleHotUpdate: Extracting failed, not in PAK: '%s'", rOutNewFilename.GetBuffer() );
	}
	else
	{
		Log( "ConsoleHotUpdate: Extracted from PAK file '%s' to: '%s'", originalFile.GetBuffer(), strTempFile.GetBuffer() );
		rOutNewFilename = strTempFile;
	}

	// make temp path-filename
	strExt = Path::GetExt( rOutNewFilename );
	strTempFile = m_strConsoleHotUpdateTempFolder;
	strTempFile = Path::AddBackslash( strTempFile );
	strTempFile += Path::GetFileName( rOutNewFilename );

	// make UID for this temp file
	const int kMaxUidStrSize = 100;
	char strUid[kMaxUidStrSize];

	sprintf_s( strUid, kMaxUidStrSize, "_uid%lld", uid );

	// append UID as string and add extension if any
	strTempFile += strUid;
	strTempFile += ( strExt == "" ? "" : "." );
	strTempFile += strExt;

	// copy original to temp file
	//TODO: add here an app wide critical section
	if( ::CopyFile( rOutNewFilename, strTempFile, FALSE ) )
	{
		// make absolute temp path as final temp file path
		strRoot = Path::GetExecutableParentDirectory();
		strRoot = Path::AddBackslash( strRoot );
		strRoot += strTempFile;
		rOutNewFilename = strRoot;

		Log( "ConsoleHotUpdate: Temp file ready for upload: '%s'", rOutNewFilename.GetBuffer() );
	}
	else
	{
		Log( "ConsoleHotUpdate: The file doesnt exists anymore, skipping: '%s'", rOutNewFilename.GetBuffer() );
		return false;
	}

	return true;
}

bool CConsoleHotUpdate::ProcessCommand( SHotUpdateJob* pJob, SHotUpdateCommand* const pCmd )
{
	switch( pCmd->m_type )
	{
		case SHotUpdateCommand::eType_DeleteTempFile:
		{
			SHotUpdateCommandDeleteTempFile* pCmdDeleteTempFile = (SHotUpdateCommandDeleteTempFile*)pCmd;
			
			Log( "ConsoleHotUpdate: Deleting temp file: '%s'", pCmdDeleteTempFile->m_tempFileName.GetBuffer() );

			if( !CFileUtil::DeleteFile( pCmdDeleteTempFile->m_tempFileName ) )
			{
				Log( "ConsoleHotUpdate: Failed to delete temp file: '%s'", pCmdDeleteTempFile->m_tempFileName.GetBuffer() );
			}

			break;
		}

		case SHotUpdateCommand::eType_ResetSoft:
		{
			ResetConsoles();
			break;
		}

		case SHotUpdateCommand::eType_ResetHard:
		{
			ResetConsoles( true );
			break;
		}

		case SHotUpdateCommand::eType_LaunchGame:
		{
			GetIEditor()->GetConsoleSync()->LaunchGameLevelOnConsole();
			break;
		}

		case SHotUpdateCommand::eType_FileCopy:
		{
			CString tempFilename;
			SHotUpdateCommandFileCopy* pCmdFile = (SHotUpdateCommandFileCopy*)pCmd;

			ULONGLONG uid = pCmdFile->m_uid;
			const char* pOriginalFilename = pCmdFile->m_originalFileName.GetBuffer();

			bool bInPak;
			CString originalFilename = pOriginalFilename;

			CryAutoLock<CryMutex> autoLock( CGameEngine::GetPakModifyMutex() );
			PrepareFileForTransfer( uid, pOriginalFilename, tempFilename, bInPak, originalFilename );

			pCmdFile->m_tempFileName = tempFilename;
			CString extension = Path::GetExt( tempFilename );
			UINT nUploadedFiles = m_pCurrentJob->m_totalFileCount - m_pCurrentJob->ComputeFileCopyCmdCount();

			if( IgnoreFileExtension( extension ) )
			{
				if( m_pCurrentJob->m_pObserver )
				{
					m_pCurrentJob->m_pObserver->OnUploadFileProgress( (CHotUpdateSystem::EConsolePlatform) -1, pOriginalFilename, nUploadedFiles, m_pCurrentJob->m_totalFileCount, SHotUpdateJobObserver::eResultCode_ExtensionNotSupported );
				}

				Log( "ConsoleHotUpdate: File extension is ignored, skipping: '%s'", pOriginalFilename );

				return false;
			}

			Log( "ConsoleHotUpdate: Uploading '%s'", pOriginalFilename );

			bool bNeedsCompiling = NeedsCompiling( extension ), bError = false;
			std::vector<CString> outputFilenames, filesToDelete;
			CString fullPath, originalFullPath;

			for( size_t i = CHotUpdateSystem::eFirstPlatform;	i < CHotUpdateSystem::eNumberOfPlatforms; ++i )
			{
				CHotUpdateSystem::EConsolePlatform aPlatform =	(CHotUpdateSystem::EConsolePlatform)i;

				if( !gSettings.oHotUpdateSystemSettings.boPlatformEnabled[i] )
				{
					continue;
				}

				outputFilenames.clear();
				filesToDelete.clear();

				fullPath = tempFilename;
				originalFullPath = bInPak ? originalFilename : pOriginalFilename;

				//
				// compiling the file, if needed
				//
				if( bNeedsCompiling )
				{
					if( m_pCurrentJob->m_pObserver )
					{
						m_pCurrentJob->m_pObserver->OnUploadFileProgress( aPlatform, pOriginalFilename, nUploadedFiles, m_pCurrentJob->m_totalFileCount, SHotUpdateJobObserver::eResultCode_NeedsCompiling );
					}

					uint32 nCompileStages = s_fileTypes[extension].m_compileStageCount;

					for( size_t j = 0; j < nCompileStages; ++j )
					{
						if( !ExecuteResourceCompiler( aPlatform, fullPath, outputFilenames, extension ) )
						{
							bError = true;
							break;
						}

						// if one file
						if( outputFilenames.size() == 1 )
						{
							fullPath = outputFilenames[0];
							extension = Path::GetExt( fullPath.GetBuffer() );
						}

						filesToDelete.insert( filesToDelete.end(), outputFilenames.begin(), outputFilenames.end() );
					}

					if( bError )
						break;
				}
				else
				{
					outputFilenames.push_back( tempFilename );
					filesToDelete.insert( filesToDelete.end(), outputFilenames.begin(), outputFilenames.end() );
				}

				bool boCopySucceeded = false;

				if (outputFilenames.empty())
				{
					Log( "ConsoleHotUpdate: File '%s' has not output files, skipping...", pOriginalFilename );
				}
				else
				{
					// copy to console by using the original full path of the asset, which will be reconstructed on the console
					boCopySucceeded = AssetCopy( aPlatform, originalFullPath.GetBuffer(), outputFilenames, uid );
				}

				if( boCopySucceeded )
				{
					AssetNotifyChange( aPlatform, bInPak ? originalFilename : pOriginalFilename );

					if( m_pCurrentJob->m_pObserver )
					{
						m_pCurrentJob->m_pObserver->OnUploadFileProgress( aPlatform, pOriginalFilename, nUploadedFiles, m_pCurrentJob->m_totalFileCount, SHotUpdateJobObserver::eResultCode_UploadSuccessful );
					}
				}
				else
				{
					bError = true;

					if( m_pCurrentJob->m_pObserver )
					{
						m_pCurrentJob->m_pObserver->OnUploadFileProgress( aPlatform, pOriginalFilename, nUploadedFiles, m_pCurrentJob->m_totalFileCount, SHotUpdateJobObserver::eResultCode_UploadError );
					}
				}

				// queue temp compiled files for deleting
				for( size_t k = 0; k < filesToDelete.size(); ++k )
				{
					SHotUpdateCommandFileCopy* pCmdFileCopy = (SHotUpdateCommandFileCopy*)pCmd;

					pJob->m_commands.push_back( new SHotUpdateCommandDeleteTempFile( filesToDelete[k] ) );
				}
			}

			return !bError;
		}
	}

	return true;
}

void CConsoleHotUpdate::ProcessNextCommand( SHotUpdateJob* pJob )
{
	if( (*pJob->m_commands.begin())->IsReadyForProcessing() )
	{
		SHotUpdateCommand* pCmd = *pJob->m_commands.begin();

		pJob->m_commands.erase( pJob->m_commands.begin() );
		m_pCurrentJob = pJob;
		ProcessCommand( pJob, pCmd );

		// if this is a file copy command then add a delete temp file command also
		if( pCmd->m_type == SHotUpdateCommand::eType_FileCopy )
		{
			SHotUpdateCommandFileCopy* pCmdFileCopy = (SHotUpdateCommandFileCopy*)pCmd;

			pJob->m_commands.push_back( new SHotUpdateCommandDeleteTempFile( pCmdFileCopy->m_tempFileName ) );
		}

		delete pCmd;
	}

	if( pJob->m_commands.empty() )
	{
		if( pJob->m_pObserver )
		{
			pJob->m_pObserver->OnUploadEnd();
		}
	}
}

bool CConsoleHotUpdate::IgnoreFileExtension( const char* pFileExtension )
{
	if( s_fileTypes.find( pFileExtension ) == s_fileTypes.end() )
	{
		return false;
	}

	return s_fileTypes[pFileExtension].m_bIgnore;
}

bool CConsoleHotUpdate::IsFileAlreadyInQueue( SHotUpdateJob* pJob, const char* pFilename )
{
	bool bAlreadyInQueue = false;

	for( std::vector<SHotUpdateCommand*>::iterator iterCmd = pJob->m_commands.begin(), iterCmdEnd = pJob->m_commands.end(); iterCmd != iterCmdEnd; ++iterCmd )
	{
		if( (*iterCmd)->m_type == SHotUpdateCommand::eType_FileCopy )
		{
			SHotUpdateCommandFileCopy* pCmdFileCopy = (SHotUpdateCommandFileCopy*) *iterCmd;

			if( pCmdFileCopy->m_originalFileName == pFilename )
			{
				bAlreadyInQueue = true;
				break;
			}
		}
	}

	if( bAlreadyInQueue )
	{
		Log( "ConsoleHotUpdate: Already in job queue, skipping '%s'", pFilename );
	}

	return bAlreadyInQueue;
}

bool CConsoleHotUpdate::NeedsCompiling( const char* pFileExtension )
{
	if( s_fileTypes.find( pFileExtension ) == s_fileTypes.end() )
	{
		return false;
	}

	return s_fileTypes[pFileExtension].m_compileStageCount != 0;
}

bool CConsoleHotUpdate::NeedsMultipassCompiling( const char* pFileExtension )
{
	if( s_fileTypes.find( pFileExtension ) == s_fileTypes.end() )
	{
		return false;
	}

	return s_fileTypes[pFileExtension].m_compileStageCount > 1;
}

bool CConsoleHotUpdate::GetCompileOptions( const char* pFileExtension, CString& rOptions )
{
	if( s_fileTypes.find( pFileExtension ) == s_fileTypes.end() )
	{
		rOptions = "";
		return false;
	}

	rOptions = s_fileTypes[pFileExtension].m_pCompileParams;

	return true;
}

CConsoleHotUpdate::SHotSyncFileType* CConsoleHotUpdate::GetHotSyncFileType( const char* pFileExtension )
{
	if( s_fileTypes.find( pFileExtension ) == s_fileTypes.end() )
	{
		return NULL;
	}

	return &s_fileTypes[pFileExtension];
}

bool CConsoleHotUpdate::ExecuteResourceCompiler( CHotUpdateSystem::EConsolePlatform aPlatform, const char* pFilename, std::vector<CString>& rOutputFilenames, const char* pFileExtension )
{
	CString settings, compileOptions;

	switch( aPlatform )
	{
		case CHotUpdateSystem::eXBOX360:
		{
			settings.Append( "/p=X360 " );
			break;
		}

		case CHotUpdateSystem::ePLAYSTATION3:
		{
			settings.Append( "/p=PS3 " );
			break;
		}

		default:
		{
			return false;
		}
	}

	settings.Append( "/refresh " );

	if( GetCompileOptions( pFileExtension, compileOptions ) )
	{
		settings.Append( compileOptions );
	}

	CString workingPath = Path::GetExecutableParentDirectory();

	workingPath = Path::AddBackslash( workingPath );
	workingPath += gSettings.strStandardTempDirectory;
	workingPath = Path::AddBackslash( workingPath );
	CFileUtil::CreatePath( workingPath );

	CString fileTemp = workingPath;
	fileTemp += Path::GetFile( pFilename );
	
	if( !::CopyFile( pFilename, fileTemp, false ) )
	{
		return false;
	}

	CString compiledTemp( workingPath );
	
	compiledTemp += "compiled\\";
	CString targetroot( " /targetroot=" );
  targetroot += compiledTemp;
  settings.Append( targetroot );

	if( CResourceCompilerHelper().CallResourceCompiler(
		fileTemp.GetBuffer(), settings.GetBuffer(), this, false,
		CResourceCompilerHelper::eRcExePath_currentFolder,
		true, true, NULL ) != CResourceCompilerHelper::eRcCallResult_success )
	{
		CFileUtil::DeleteFile( fileTemp );
		return false;
	}

	CString fileNameTemp( compiledTemp );
	CString fileName;

	fileNameTemp += Path::GetFile( pFilename );
	rOutputFilenames.clear();
	size_t i = 0;

	// search for XXX.n split files (like DDS.0)
	while( true )
	{
		fileName.Format( "%s.%d", fileNameTemp.GetBuffer(), i );

		if( !CFileUtil::FileExists( fileName ) )
		{
			break;
		}

		rOutputFilenames.push_back( fileName );
		++i;
	}

	//
	// search for files which can possibly be output of RC compilation
	//
	SHotSyncFileType* pFileTypeInfo = GetHotSyncFileType( pFileExtension );

	if( pFileTypeInfo && pFileTypeInfo->m_pOutputFileExtensions )
	{
		std::vector<CString> extensions;
		
		SplitString( CString( pFileTypeInfo->m_pOutputFileExtensions ), extensions, ',' );

		for( size_t i = 0; i < extensions.size(); ++i )
		{
			fileName.Format( "%s.%s", Path::RemoveExtension(fileNameTemp).GetBuffer(), extensions[i].GetBuffer());

			if( !CFileUtil::FileExists( fileName ) )
			{
				continue;
			}

			// add the compiled temp file
			rOutputFilenames.push_back( fileName );
		}
	}

	// delete the original uncompiled temp file
	CFileUtil::DeleteFile( fileTemp );

	return true;
}

bool CConsoleHotUpdate::AssetCopy( CHotUpdateSystem::EConsolePlatform aPlatform, const char* pOriginalFilename, std::vector<CString>& rFilenames, ULONGLONG uid )
{
	CString destination = gSettings.oHotUpdateSystemSettings.GetPlatformConsoleRootDirectory( aPlatform, CString() );
	
	if( destination.IsEmpty() )
	{
		return false;
	}

	CString fileRelative, fileDestination, strUid;

	strUid.Format( "_uid%lld", uid );

	destination = Path::AddBackslash( destination );
	fileRelative = Path::GetRelativePath( pOriginalFilename );

	// on PS3 we have all path and name in lowercase
	if( aPlatform == CHotUpdateSystem::ePLAYSTATION3 )
	{
		fileRelative.MakeLower();
	}
	
	destination += Path::GetPath( fileRelative );

	for( size_t i = 0; i < rFilenames.size(); ++i )
	{
		fileDestination = destination;
		fileDestination += Path::GetFile( rFilenames[i] );
		
		if( aPlatform == CHotUpdateSystem::ePLAYSTATION3 )
		{
			fileDestination.Replace( '\\', '/' );
		}
		else
		{
			fileDestination.Replace( '/', '\\' );
		}

		fileDestination.Replace( strUid, "" );

		if( !GetIEditor()->GetConsoleSync()->GetConsoleConnection()->FileCopyTo( aPlatform, rFilenames[i], fileDestination ) )
		{
			return false;
		}
	}

	return true;
}

void CConsoleHotUpdate::SyncThreadData()
{
	CryAutoLock<CryMutex> autoLockSync( m_syncMutex );

	// update main job
	for( size_t i = 0; i < m_mainJobInfo.m_commands.size(); ++i )
	{
		if( m_mainJobInfo.m_commands[i]->m_type == SHotUpdateCommand::eType_FileCopy )
		{
			SHotUpdateCommandFileCopy* pCmdFileCopy = (SHotUpdateCommandFileCopy*)m_mainJobInfo.m_commands[i];

			if( IsFileAlreadyInQueue( &m_mainJob, pCmdFileCopy->m_originalFileName ) )
			{
				delete pCmdFileCopy;
				continue;
			}
		}

		m_mainJob.m_commands.push_back( m_mainJobInfo.m_commands[i] );
	}

	m_mainJob.m_totalFileCount = m_mainJob.ComputeFileCopyCmdCount();
	m_mainJob.m_pObserver = m_mainJobInfo.m_pObserver;
	m_mainJobInfo.m_commands.clear();
	m_mainJobInfo.m_totalFileCount = 0;

	// update with new jobs
	for( size_t i = 0; i < m_newJobs.size(); ++i )
	{
		m_jobs.push_back( m_newJobs[i] );
		m_newJobs[i]->m_totalFileCount = m_newJobs[i]->ComputeFileCopyCmdCount();
	}

	// update from new job commands, with existing jobs
	for( TNewJobCommands::iterator iter = m_newJobCommands.begin(), iterEnd = m_newJobCommands.end(); iter != iterEnd; ++iter )
	{
		SHotUpdateJob* pJob = iter->first;
		std::vector<SHotUpdateJob*>::iterator jobIter = std::find( m_jobs.begin(), m_jobs.end(), pJob );

		if( jobIter != m_jobs.end() )
		{
			for( size_t j = 0; j < iter->second.size(); ++j )
			{
				if( iter->second[j]->m_type == SHotUpdateCommand::eType_FileCopy )
				{
					SHotUpdateCommandFileCopy* pCmdFileCopy = (SHotUpdateCommandFileCopy*)iter->second[j];

					if( IsFileAlreadyInQueue( (*jobIter), pCmdFileCopy->m_originalFileName ) )
					{
						delete pCmdFileCopy;
						continue;
					}
				}

				(*jobIter)->m_commands.push_back( iter->second[j] );
			}

			(*jobIter)->m_totalFileCount = (*jobIter)->ComputeFileCopyCmdCount();
		}
	}

	// clear the new processed/added jobs
	m_newJobs.clear();

	// update status
	m_jobsDone.clear();

	// done fetching new commands for existing jobs
	m_newJobCommands.clear();

	for( size_t i = 0; i < m_jobs.size(); ++i )
	{
		m_jobsDone[(TJobHandle)m_jobs[i]] = m_jobs[i]->m_commands.empty();
	}

	// abort jobs
	for( std::set<TJobHandle>::iterator iter = m_jobsToAbort.begin(), iterEnd = m_jobsToAbort.end(); iter != iterEnd; ++iter )
	{
		std::vector<SHotUpdateJob*>::iterator jobIter = std::find( m_jobs.begin(), m_jobs.end(), (SHotUpdateJob*)(*iter) );

		if( jobIter != m_jobs.end() )
		{
			(*jobIter)->DeleteCommands();
			(*jobIter)->m_totalFileCount = 0;

			if( (*jobIter)->m_pObserver )
			{
				(*jobIter)->m_pObserver->OnUploadEnd( true );
			}
		}
	}

	// delete requested jobs
	for( std::set<TJobHandle>::iterator iter = m_jobsToDelete.begin(), iterEnd = m_jobsToDelete.end(); iter != iterEnd; ++iter )
	{
		std::vector<SHotUpdateJob*>::iterator jobIter = std::find( m_jobs.begin(), m_jobs.end(), (SHotUpdateJob*)(*iter) );

		if( jobIter != m_jobs.end() )
		{
			delete (SHotUpdateJob*)(*iter);
		}
	}

	// reinitialize lists
	m_jobsToDelete.clear();
	m_jobsToAbort.clear();
	autoLockSync.~CryAutoLock();

	// allow other threads to get a lock on the data members
	Sleep( 10 );
}

void CConsoleHotUpdate::Run()
{
	CryThreadSetName( -1, "ConsoleHotUpdate" );
	m_bRun = true;

	while( m_bRun )
	{
		bool bLoop = true;

		while( bLoop )
		{
			SyncThreadData();
			bLoop = ( m_bRun && m_jobs.empty() && m_mainJob.m_commands.empty() );

			if( m_bPaused )
			{
				bLoop = true;
			}
		}

		if( !m_bRun )
		{
			break;
		}

		//
		// do main job
		//
		if( !m_mainJob.m_commands.empty() )
		{
			ProcessNextCommand( &m_mainJob );
		}

		//
		// do other jobs
		//
		std::vector<SHotUpdateJob*>::iterator iter = m_jobs.begin();
		
		while( iter != m_jobs.end() )
		{
			SHotUpdateJob* pJob = *iter;

			if( pJob->m_commands.empty() )
			{
				++iter;
				continue;
			}

			ProcessNextCommand( pJob );

			++iter;
		}
	}

	m_bRun = false;
}

bool CConsoleHotUpdate::AssetNotifyChange( CHotUpdateSystem::EConsolePlatform aPlatform, const char* pOriginalFilename )
{
	CString fileRelative = Path::GetRelativePath( pOriginalFilename );
	INotificationNetworkClient* pClient = GetIEditor()->GetConsoleSync()->GetConsoleConnection()->NotificationBegin( aPlatform );
	
	if( !pClient )
	{
		return false;
	}

	fileRelative = Path::GetRelativePath( pOriginalFilename, true );
	fileRelative.Replace( '\\', '/' );
	fileRelative.MakeLower();
	bool bSent = pClient->Send( "HotUpdate", fileRelative, fileRelative.GetLength() + 1 );

	GetIEditor()->GetConsoleSync()->GetConsoleConnection()->NotificationEnd( aPlatform );

	return true;
}

void CConsoleHotUpdate::OnRCMessage( MessageSeverity aSeverity, const char* pText )
{
	if( m_pCurrentJob )
	{
		if( m_pCurrentJob->m_pObserver )
		{
			m_pCurrentJob->m_pObserver->OnRcMessage( m_currentRcPlatform, aSeverity, pText );
		}
	}
}

void CConsoleHotUpdate::ExportAndLaunch()
{
	CString	strLevelFolder( GetIEditor()->GetLevelFolder() );
	strLevelFolder = Path::AddBackslash( strLevelFolder );
	CString strLevelFile( strLevelFolder );
	strLevelFile += "level.pak";
	CString strTerrainTexture( strLevelFolder );
	strTerrainTexture += "terraintexture.pak";

	// it will export the level, in case it hasn't been exported yet or it's modified.
	if( ( !( CFileUtil::FileExists( strLevelFile ) && CFileUtil::FileExists( strTerrainTexture ) ) ) ||	!GetIEditor()->IsLevelExported() )
	{
		CGameExporter gameExporter;
	
		// exports the level using sensible defaults for most cases.
		gameExporter.ExportFast( true, false, true, GetPlatformEndian(), NULL, false, true );
	}

	m_hExportAndLaunchJob = AddFile( strLevelFile.GetBuffer(), m_hExportAndLaunchJob, NULL );
	AddLaunchCommand( m_hExportAndLaunchJob );
}
